/**
 * 该模块提供事件机制。
 * License: BSD
 * Authors: Lucifer (786325481@QQ.com)
 * Copyright: Copyright (C) 2008 Lucifer. All rights reserved.
 */
module system.Event;

import system.runtime.c.string;

/**
 * 表示将处理事件的方法。
 * Comments: 大多数情况下，我们应该使用这个模板。
 *           如果想自定义事件参数，请使用 Event(T...) 。
 */
public template EventHandler(T, TEventArg : EventArgs = EventArgs)
{
	alias Event!(T, TEventArg) EventHandler;
}

/**
 * 用于声明事件的事件机制。
 * Comments: 类似.NET的事件。
 */
public struct Event(T...)
{
	/**
	 * 事件所需要的委托或函数指针类型。
	 */
	alias void delegate(T) TDelegate;
	/// ditto
	alias void function(T) TFunction;

	private struct EventInfo
	{
		/*
		 * 表明传进来的委托或函数指针类型。
		 */
		enum Type
		{
			Delegate,
			Function
		}
		/*
		 * 存储传进来的委托或函数指针
		 */
		union
		{
			TDelegate delegateItem;
			TFunction functionItem;
		}

		Type type;

		/*
		 * 构造函数。
		 */
		public this(TDelegate dg)
		{
			type = Type.Delegate;
			delegateItem = dg;
		}
		/*
		 * ditto
		 */
		public this(TFunction fn)
		{
			type = Type.Function;
			functionItem = fn;
		}

		/*
		 * 比较两个 EventInfo 是否相等。
		 */
		public bool opEquals(EventInfo other)
		{
			if(this.type == Type.Delegate)
			{
				return this.type == other.type && this.delegateItem == other.delegateItem;
			}
			else
			{
				return this.type == other.type && this.functionItem == other.functionItem;
			}
		}
		/*
		 * 引发委托或函数指针调用。
		 */
		public void invoke(T args)
		{
			if( type == Type.Delegate && delegateItem !is null )
			{
				delegateItem(args);
			}
			else if( type == Type.Function && functionItem !is null )
			{
				functionItem(args);
			}
		}
	}
	/*
	 * 多数情况下，事件订阅的数量少于8个，甚至少于4个
	 * 这里我们把默认的事件订阅量设为4，让它以4的指数倍增长。
	 */
	private const int defaultCapacity = 4;

	private int theSize;
	private EventInfo[] theList;

	/*
	 * 注册委托或函数指针。
	 */
	private void add(EventInfo eventInfo)
	{
		auto size = theList.length;

		if( size == 0 )
		{
			theList.length = defaultCapacity;
		}
		if( size == theSize )
		{
			theList.length = 2 * theList.length;
		}

		theList[theSize++] = eventInfo;
	}

	/*
	 * 删除某个位置的委托或函数指针。
	 */
	private void removeAt(int index)
	{
		theSize--;
		if( index < theSize )
		{
			memmove(theList[index..length].ptr, theList[index+1..length].ptr, (theSize-index) * EventInfo.sizeof);
		}
		theList[theSize] = EventInfo.init;
	}

	/*
	 * 查看是否有委托或函数指针注册。
	 */
	private bool isEmpty()
	{
		return theSize == 0;
	}

	/*
	 * 查找委托或函数指针在委托列表中的位置。
	 * 找到返回该位置；找不到则返回-1。
	 */
	private int indexOf(EventInfo eventInfo)
	{
		for( auto i = 0; i < theSize; i++ )
		{
			if( theList[i] == eventInfo )
			{
				return i;
			}
		}
		return -1;
	}

	/**
	 * 注册委托。
	 * Params: dg = 要注册的委托。
	 *         它的类型是 void delegate(T...) 。
	 * Comments: 该实现是线程安全和异常安全的。
	 *           当有异常发生时，直接返回。
	 */
	public void add(TDelegate dg)
	in
	{
		assert( dg !is null );
	}
	body
	{
		synchronized
		{

			auto eventInfo = EventInfo(dg);

			scope(failure) return;

			if( !isEmpty() )
			{
				/*
				 * 先查看是否包含该委托。
				 * 没有则注册，否则什么也不做。
				 */
				if(indexOf(eventInfo) == -1)
				{
					add(eventInfo);
				}
			}
			else
			{
				/*
				 * 到这里，没有委托注册，直接进行注册。
				 */
				add(eventInfo);
			}
		}
	}

	/**
	 * ditto
	 */
	public void add(TFunction fn)
	in
	{
		assert( fn !is null );
	}
	body
	{
		synchronized
		{
			auto eventInfo = EventInfo(fn);

			scope(failure) return;

			if( !isEmpty() )
			{
				if(indexOf(eventInfo) == -1)
				{
					add(eventInfo);
				}
			}
			else
			{
				add(eventInfo);
			}
		}
	}

	/**
	 * ~= 操作符重载。
	 */
	alias add opCatAssign;

	/**
	 * 移除注册的委托。
	 * Params: dg = 要移除的委托。
	 *         它的类型是 void delegate(T...) 。
	 * Returns: 返回是否成功移除委托。
	 *          如果成功，返回 true 。否则，返回 false 。
	 * Comments: 该实现是线程安全和异常安全的。
	 *           当有异常发生时，直接返回。
	 */
	public bool remove(TDelegate dg)
	in
	{
		assert( dg !is null );
	}
	body
	{
		synchronized
		{
			auto eventInfo = EventInfo(dg);

			scope(failure) return false;

			auto index = indexOf(eventInfo);

			/*
			 * 先找到要移除的委托在委托列表中的位置。
			 * 找到了就移除，否则什么也不做。
			 */
			if( index != -1 )
			{
				removeAt(index);
			}

			return index != -1;
		}
	}

	/**
	 * ditto
	 */
	public bool remove(TFunction fn)
	in
	{
		assert( fn !is null );
	}
	body
	{
		synchronized
		{
			auto eventInfo = EventInfo(fn);

			scope(failure) return false;

			auto index = indexOf(eventInfo);

			if( index != -1 )
			{
				removeAt(index);
			}

			return index != -1;
		}
	}

	/**
	 * -= 操作符重载。
	 */
	alias remove opSubAssign;

	/**
	 * 引发委托或函数指针调用。
	 * Comments: 该实现是线程安全和异常安全的。
	 */
	public void invoke(T args)
	{
		synchronized
		{

			for( auto i = 0; i < theSize; i++ )
			{
				scope(failure) break;
				theList[i].invoke(args);
			}
		}
	}

	alias invoke opCall;

	/**
	 * 比较两个 Event(T...) 是否相等。
	 */
	public bool opEquals(Event!(T) other)
	{
		for(auto i = 0; i < theSize; i++)
		{
			if( theList[i] != other.theList[i] )
			{
				return false;
			}
		}
		return true;
	}
}

/**
 * 包含事件数据的基类。
 */
public class EventArgs
{
	private static EventArgs theEmpty;

	public static this()
	{
		theEmpty = new EventArgs();
	}

	public this()
	{
	}

	/**
	 * 表示没有事件数据的事件。
	 */
	public static EventArgs empty()
	{
		return theEmpty;
	}
}

unittest
{
	class Button
	{
		Event!(Button) press;
		EventHandler!(Button, EventArgs) clicked;
	}

	int count = 0;
	int numeric = 0;

	void wasPressedA(Button b)
	{
		count++;
	}

	void clickedA(Button sender, EventArgs e)
	{
		numeric++;
	}

	void clickedB(Button sender, EventArgs e)
	{
		numeric++;
	}

	void wasPressedB(Button b)
	{
		count++;
	}

	Button b = new Button();

	// 测试 add()
	b.press.add(&wasPressedA);
	// 测试 invoke()
	b.press(b);

	b.clicked.add(&clickedA);
	b.clicked(b, EventArgs.empty);

	assert( count == 1 );
	assert( numeric == 1 );

	b.press ~= &wasPressedB;
	b.press.invoke(b);

	b.clicked ~= (&clickedB);
	b.clicked.invoke(b, EventArgs.empty);

	assert( count == 3 );
	assert( numeric == 3 );

	// 测试 remove()
    b.press.remove(&wasPressedA);
	b.press(b);

	b.clicked.remove(&clickedA);
	b.clicked(b, EventArgs.empty);

	assert( count == 4 );
	assert( numeric == 4 );

	b.press -= &wasPressedB;
	b.press(b);

	b.clicked.remove(&clickedB);
	b.clicked(b, EventArgs.empty);

	assert( count == 4 );
	assert( numeric == 4 );
}
